﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using FxSuperCore.Utils;
using SimpleHttpServer.Exceptions;
using SimpleHttpServer.Handler;
using SimpleHttpServer.MIME;
using HandleHttp = SimpleHttpServer.HttpHandlerRegister.HandleHttp;

namespace SimpleHttpServer
{
	public class HttpServer : IDisposable
	{
		protected HttpListener _httpListener;

		// 静态资源目录
		protected string StaticFolder { get; set; }

		// 是否采用多线程处理
		protected bool MultiThread { get; set; }

		// 是否在发送错误时抛出
		public bool ThrowOnError { get; set; }

		// 监听端口
		public int Port { get; private set; }

		// 请求注册器
		protected HttpHandlerRegister HttpRegister { get; set; }

		public HttpServer()
		{
			HttpRegister = new HttpHandlerRegister();
		}

		/// <summary>
		/// 设置静态资源文件夹
		/// </summary>
		/// <param name="path">文件夹路径</param>
		public virtual void SetStaticFolder(string path)
		{
			if (!Directory.Exists(path))
			{
				throw new ArgumentException($"指定的文件夹路径不存在:[{path}]");
			}

			StaticFolder = path;
		}

		/// <summary>
		/// 是否开启多线程处理请求
		/// </summary>
		/// <param name="startMultiThread">是否开启</param>
		public virtual void SetMultiThread(bool startMultiThread)
		{
			MultiThread = startMultiThread;
		}

		/// <summary>
		/// 启动HttpServer
		/// </summary>
		/// <param name="port">监听的端口</param>
		public virtual void Listen(int port)
		{
			_httpListener = new HttpListener();
			try
			{
				_httpListener.Prefixes.Add($"http://+:{port}/");
				_httpListener.Start();
				Port = port;
				CommonUtils.ChangeFirewallRule($"Fx_SimpleHtpServer_Rule_{Port}", (ushort) port);
			}
			catch (System.Exception e)
			{
				CommonUtils.ChangeFirewallRule($"Fx_SimpleHtpServer_Rule_{Port}", (ushort)port, false);
				throw new ArgumentException(e.Message, e);
			}

			_httpListener.BeginGetContext(AcceptHttpRequest, null);
		}

		protected async virtual void AcceptHttpRequest(IAsyncResult asyncResult)
		{
			HttpListenerContext context = null;
			try
			{
				_httpListener.BeginGetContext(AcceptHttpRequest, null);
				context = _httpListener.EndGetContext(asyncResult);

				AsyncHandleRequestResult handleResult = await ProceedRequest(context);
				if (handleResult.Result) return;

				throw handleResult.FailException;
			}
			catch (System.Exception e) when (e is ObjectDisposedException)
			{
				// ignore
			}
			catch (HttpNotMatchException e)
			{
				if (ThrowOnError)
				{
					throw new HttpNotMatchException($"处理请求异常,URL:[{context?.Request.Url}] [{e.Message}]");
				}
			}
			catch (System.Exception e)
			{
				if (e.Message.Contains("指定的网络名不再可用"))
				{
					return;
				}

				if (ThrowOnError)
				{
					throw new System.Exception($"处理请求异常,URL:[{context?.Request.Url}] [{e.Message}]");
				}
			}
		}

		/// <summary>
		/// 处理请求
		/// </summary>
		/// <param name="ctx">请求上下文</param>
		/// <returns>处理结果</returns>
		private Task<AsyncHandleRequestResult> ProceedRequest(HttpListenerContext ctx)
		{
			if (MultiThread)
			{
				return Task.Run(() => ProceedRequestImpl(ctx));
			}
			else
			{
				return ProceedRequestImpl(ctx);
			}
		}

		/// <summary>
		/// 处理请求的实现
		/// </summary>
		/// <param name="ctx">请求上下文</param>
		/// <returns>处理结果</returns>
		private async Task<AsyncHandleRequestResult> ProceedRequestImpl(HttpListenerContext ctx)
		{
			try
			{
				// 处理静态资源
				if (await ProceedStaticResource(ctx)) return AsyncHandleRequestResult.Success();
				FxHttpContext fxHttpContext = new FxHttpContext(ctx);
				// 自定义handler处理
				if (!HttpRegister.Handle(fxHttpContext))
				{
					fxHttpContext.Response.Status = (int)HttpStatusCode.NotFound;
					fxHttpContext.Response.SetBody("404 Error NotFind");
					fxHttpContext.Response.ReturnBody();
					throw new HttpNotMatchException("未匹配到路径选择器");
				}
			}
			catch (System.Exception e)
			{
				return AsyncHandleRequestResult.Failed(e);
			}

			return AsyncHandleRequestResult.Success();
		}

		/// <summary>
		/// 处理静态资源
		/// </summary>
		/// <param name="ctx">请求上下文</param>
		/// <returns>是否已经处理过该静态资源</returns>
		protected virtual async Task<bool> ProceedStaticResource(HttpListenerContext ctx)
		{
			if (StaticFolder == null) return false;
			string path = ctx.Request.Url.AbsolutePath;
			string filepath = StaticFolder + path;
			if (!File.Exists(filepath)) return false;
			string ext = Path.GetExtension(filepath).Trim('.');
			ctx.Response.ContentType = HttpMimeType.PLAIN.GetMimeTypeByFileExt(ext);
			using (var bs = new BufferedStream(ctx.Response.OutputStream))
			using (var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				ctx.Response.ContentLength64 = fs.Length;
				ctx.Response.KeepAlive = true;
				ctx.Response.SendChunked = false;
				await fs.CopyToAsync(bs, 40960);
			}
			return true;
		}

		/// <summary>
		/// 注册一个Get方法
		/// </summary>
		/// <param name="urlPattern">url-path匹配项</param>
		/// <param name="httpHandler">回调函数</param>
		public virtual void Get(string urlPattern, HandleHttp httpHandler)
		{
			RegisterHandler("GET", urlPattern, httpHandler);
		}

		/// <summary>
		/// 注册一个Post方法
		/// </summary>
		/// <param name="urlPattern">url-path匹配项</param>
		/// <param name="httpHandler">回调函数</param>
		public virtual void Post(string urlPattern, HandleHttp httpHandler)
		{
			RegisterHandler("POST", urlPattern, httpHandler);
		}

		/// <summary>
		/// 注册一个HttpHandler
		/// </summary>
		/// <param name="method">请求方法(大写，例:GET/POST)</param>
		/// <param name="urlPattern">url-path匹配项</param>
		/// <param name="httpHandler">回调函数</param>
		public virtual void RegisterHandler(string method, string urlPattern, HandleHttp httpHandler)
		{
			HttpRegister.RegisterHandler(new HttpHandlerWapper(urlPattern, method, httpHandler, false));
		}

		/// <summary>
		/// 注册一个HttpHandler
		/// </summary>
		/// <param name="handler">处理器</param>
		public virtual void RegisterHandler(HttpHandlerWapper handler)
		{
			HttpRegister.RegisterHandler(handler);
		}

		public virtual void Close()
		{
			_httpListener?.Stop();
			_httpListener?.Close();
			StaticFolder = null;
			MultiThread = false;
			try
			{
				if (Port > 0)
				{
					CommonUtils.ChangeFirewallRule($"Fx_SimpleHtpServer_Rule_{Port}", (ushort)Port, false);
				}
			}
			catch (System.Exception)
			{
				// ignore
			}
		}

		public void Dispose()
		{
			try
			{
				Close();
			}
			catch (System.Exception)
			{
				// ignore
			}
		}
	}

	public class AsyncHandleRequestResult
	{
		public bool Result { get; }

		public System.Exception FailException { get; }

		public AsyncHandleRequestResult(bool result, System.Exception failException)
		{
			Result = result;
			FailException = failException;
		}

		public static AsyncHandleRequestResult Success()
		{
			return new AsyncHandleRequestResult(true, null);
		}

		public static AsyncHandleRequestResult Failed(System.Exception e)
		{
			return new AsyncHandleRequestResult(false, e);
		}
	}


	/// <summary>
	/// 标注在方法上，表示要注册进HttpServer的方法
	/// </summary>
	[AttributeUsage(AttributeTargets.Method)]
	public class RequestMappingAttribute : Attribute
	{
		public string Method { get; }

		public string UrlPattern { get; }

		public bool AllocCrossOrigin { get; }

		public RequestMappingAttribute(string method, string urlPattern) : this(method, urlPattern, false){}

		public RequestMappingAttribute(string method, string urlPattern, bool allocCrossOrigin)
		{
			Method = method;
			UrlPattern = urlPattern;
			AllocCrossOrigin = allocCrossOrigin;
		}
	}

	public static class HttpServerExtension
	{
		/// <summary>
		/// 扫描指定对象中的所有可注册方法，注册进httpServer，对应可是静态类或者实例
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="server"></param>
		/// <param name="obj"></param>
		public static void ScanHandlerRegister<T>(this HttpServer server, T obj)
		{
			MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
			foreach (MethodInfo methodInfo in methods)
			{
				RequestMappingAttribute requestMapping = methodInfo.GetCustomAttribute<RequestMappingAttribute>();
				if (requestMapping == null) continue;
				Delegate @delegate = null;
				if (methodInfo.IsStatic)
				{
					@delegate = Delegate.CreateDelegate(typeof(HandleHttp), null, methodInfo);
				}
				else
				{
					@delegate = Delegate.CreateDelegate(typeof(HandleHttp), obj, methodInfo);
				}

				if (@delegate != null)
				{
					server.RegisterHandler(new HttpHandlerWapper(
						requestMapping.UrlPattern,
						requestMapping.Method.ToString(),
						(HandleHttp)@delegate,
						requestMapping.AllocCrossOrigin));
				}
			}
		}
	}
}
